package p2pmodule;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import core.Util;

import events.FileDownloadedEvent;
import events.FileDownloadedListener;
import events.TransferFinishedEvent;
import events.TransferFinishedListener;

/**
 * This class represents a transfer between two (Dust) entities from the
 * client side.
 * 
 * @author ole
 */
public class ClientTransfer extends Transfer {
	// Class constants.
	private final int SOCKET_TIMEOUT = 30 * 1000; // In milliseconds.
	
	// Class attributes.
	private Item item2download = null;
	private List<FileDownloadedListener> fd_listeners = null;
	private String download_dir = null;
	private Socket socket = null;
	private volatile boolean stop = false;

	/**
	 * Class constructor.
	 * 
	 * @param item:
	 * 		The Item to download.
	 * @param download_dir:
	 * 		The direcotory where we must put the item once downloaded.
	 */
	public ClientTransfer(Item item, String download_dir) {
		super();
		item2download = item;
		fd_listeners = new ArrayList<FileDownloadedListener>();
		this.download_dir = Util.checkDirectoryPath(download_dir);
		setName("ClientTransfer for file " + item2download.getName());
	}
	
	/**
	 * Adds a new Listener for when the file is finally downloaded.
	 * 
	 * @param listener:
	 * 		The Listener to be executed when the file is already downloaded.
	 */
	public void addFileDownloadedListener(FileDownloadedListener listener) {
		fd_listeners.add(listener);
	}
	
	/**
	 * Start handling the connection. We first sends the filename we are
	 * searching for through the socket and then we read it from the socket.
	 * 
	 * The Transfer instance will check if a file with the same name and size
	 * exists in the downloads directory. In that case it will not download it
	 * again but will raised the events as if it was.
	 */
	@Override
	public void run() {
		TransferFinishedEvent tf_event;
		FileDownloadedEvent fd_event;
		OutputStream os;
		InputStream is;
		BufferedInputStream bis;
		File fd;
		FileOutputStream fos;
		InetAddress ip_addr;
		String fname;
		int port, bytes_readed;
		byte buffer[];
	
		// Create the connection to the server.
		ip_addr = item2download.getIp();
		port = item2download.getPort();
		try {
			socket = new Socket(ip_addr, port);
			socket.setSoTimeout(SOCKET_TIMEOUT);
		
			// Check that the connection was made.
			if (!socket.isConnected())
				throw new RuntimeException("Cannot start the connection " +
										   "to " + ip_addr.toString() +
										   ":" + port + ".");
		
			// Send the filename.
			os = socket.getOutputStream();
			os.write(item2download.getName().getBytes());
			
			// Create the file in our temp directory.
			/** TODO: Here we should check that filename doesn't contains
			 * backward references... You don't want something like:
			 * temp_dust/../../<as many as needed>/etc/passwd, do you?. **/
			fname = download_dir + item2download.getName();
			fd = new File(fname);
			fd.createNewFile();
			fos = new FileOutputStream(fd);
		
			// Prepare buffers and streams for read from socket and write
			// to file.
			is = socket.getInputStream();
			bis = new BufferedInputStream(is);
			buffer = new byte[item2download.getSize()];
			
			// Now start to read the file content and save it to our temp
			// directory. We will try to read until the server close the
			// socket or the read() method returns a fail.
			bytes_readed = bis.read(buffer);
			while (!socket.isClosed() && bytes_readed != -1) {
				fos.write(buffer, 0, bytes_readed);
				bytes_readed = bis.read(buffer);
			}
			
			fos.close();
			socket.close();
		}
		// We can reach here either because of problems in the connection or
		// because someone has asked us to stop.
		catch (IOException e) {
			// Problems in the connection.
			if (!stop)
				e.printStackTrace();
		}
		
		// Create the even of file downloaded and call the listeners.
		fd_event = new FileDownloadedEvent(this, item2download.getName());
		for (FileDownloadedListener listener: fd_listeners)
			listener.handleEvent(fd_event);
		
		// Create the even of transfer finished and call the listeners.
		tf_event = new TransferFinishedEvent(this);
		for (TransferFinishedListener listener: tf_listeners)
			listener.handleEvent(tf_event);
		
		// It has to call finalize since it is a DispatchableTask.
		this.finalize();
	}
	
	/**
	 * Implementation of the stopTranfer() method of superclass Transfer.
	 * 
	 * Finish the transfer.
	 */
	public void stopTransfer() {
		stop = true;
		if (socket != null)
			try {
				socket.close();
			}
			catch (IOException e) {}
	}
}
